﻿using System;
using System.IO;
using System.Net;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
using System.Xml;
using NCMIS.AccessControl;
using NCMIS.ObjectModel;
using NCMIS.ObjectModel.MetaData;
using NCMIS.ObjectModel.Rest;
using NCMIS.Produce;
using NCMIS.Provider;
using NCMIS.ServiceModel.Syndication;

namespace NCMIS.ServiceModel.Ra
{
    /// <summary>
    /// Provides the Object Services for the REST/Atom binding. The object services provide CRUD (Create, Retrieve, Update, Delete) operations on objects in
    /// a repository.
    /// </summary>
    public partial class Service : IService
    {
        /// <summary>
        /// Creates document with content stream (used by createChildren() in NavigationService).
        /// </summary>
        /// <param name="repositoryId"></param>
        /// <param name="properties"></param>
        /// <param name="folderId"></param>
        /// <param name="contentStream"></param>
        /// <param name="versioningState"></param>
        /// <param name="policies"></param>
        /// <param name="addACEs"></param>
        /// <param name="removeACEs"></param>
        /// <returns></returns>
        public Atom10ItemFormatter CreateDocument(string repositoryId, CmisProperties properties, string folderId, ContentStream contentStream, VersioningState versioningState, string[] policies, AccessControlList addACEs, AccessControlList removeACEs)
        {
            IncomingWebRequestContext ictx = WebOperationContext.Current.IncomingRequest;
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;

            // Repository ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || properties == null)
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and inputObject must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Store the created document as a CMIS object
            CmisObject outputObject = ObjectProviderManager.Provider.CreateDocument(repositoryId, properties, folderId, contentStream, versioningState, policies, addACEs, removeACEs);
            
            // Convert the object to a syndication item
            CmisSyndicationItem outputEntry = CmisSyndicationItem.CreateEntry(repositoryId, outputObject);

            // Response properties
            ctx.ContentType = CmisContentType.Entry;
            ctx.StatusCode = HttpStatusCode.Created;
            ctx.Headers.Add("Location", string.Format("{0}{1}/object/{2}", _baseUri.AbsoluteUri, repositoryId, outputObject.Id));

            // Format the entry as an Atom entry
            return new Atom10ItemFormatter(outputEntry);
        }

        /// <summary>
        /// Creates a folder object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="properties">The property values that must be applied to the newly-created folder object.</param>
        /// <param name="folderId">The id of the folder that must be parent folder for the newly-created folder object.</param>
        /// <param name="policies">A list of policy ids that must be applied to the newly-created document. (Optional)</param>
        /// <param name="addACEs">A list of ACEs that must be applied to the newly-created folder, either using ACL from folderId if specified, or being applied if no folder is specified. (Optional)</param>
        /// <param name="removeACEs">A list of ACEs that must be removed from the newly-created folder, either using ACL from folderId if specified, or being applied if no folder is specified. (Optional)</param>
        /// <returns>A <see cref="System.String"/> holding the id of the newly created folder.</returns>
        [WebInvoke(Method = "POST", UriTemplate = "/{repositoryId}/folder/{folderId}")]
        public Atom10ItemFormatter CreateFolder(string repositoryId, string folderId, SyndicationItem inputEntry)
        {
            IncomingWebRequestContext ictx = WebOperationContext.Current.IncomingRequest;
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;

            string contentType = ictx.ContentType;

            CmisObject inputObject = CmisSyndicationItem.GetObject(inputEntry);

            // Repository ID and folder ID must be specified. Folder properties must not be null
            if (String.IsNullOrEmpty(repositoryId) || inputObject.Properties == null || String.IsNullOrEmpty(folderId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId, properties and folderId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // This solution expects all the needed information as a part of the cmis:properties element. Some clients do not inlcude the name of the folder
            // in the cmis:properties. In that case fetch the name from the atom:title element. 
            if (inputObject.Properties.Name == null)
            {
                inputObject.Properties.Add(new CmisPropertyString() { DisplayName = "Name", LocalName = "Name", PropertyDefinitionId = CmisPropertyDefinitionId.Name, QueryName = CmisPropertyDefinitionId.Name, Value = new string[] { inputEntry.Title.Text } });
            }

            // Store the created folder as a CMIS object
            // TODO: fetch addACEs and RemoveACEs, policyIds (inputObject.PolicyIds.id)
            CmisObject outputObject = ObjectProviderManager.Provider.CreateFolder(repositoryId, inputObject.Properties, folderId, null,
                new AccessControlList(), new AccessControlList());

            // Convert the object to a syndication item
            CmisSyndicationItem outputEntry = CmisSyndicationItem.CreateEntry(repositoryId, outputObject);

            // Response properties
            ctx.ContentType = CmisContentType.Entry;
            ctx.StatusCode = HttpStatusCode.Created;
            ctx.Headers.Add("Location", string.Format("{0}{1}/object/{2}", _baseUri.AbsoluteUri, repositoryId, outputObject.Id));

            // Format the entry as an Atom entry
            return new Atom10ItemFormatter(outputEntry);
        }

        /// <summary>
        /// Creates a relationship object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="properties">The property values that must be applied to the newly-created relationship object.</param>
        /// <param name="policies">A list of policy ids that must be applied to the newly-created relationship object. (Optional)</param>
        /// <param name="addACEs">A list of ACEs that must be applied to the newly-created relationship object, either using ACL from folderId if specified, or being applied if no folder is specified. (Optional)</param>
        /// <param name="removeACEs">A list of ACEs that must be removed from the newly-created relationship object, either using ACL from folderId if specified, or being applied if no folder is specified. (Optional)</param>
        /// <returns>A <see cref="System.String"/> holding the id of the newly created relationship object.</returns>
        //[WebInvoke(Method = "POST", UriTemplate = "/{repositoryId}/relationship", BodyStyle = WebMessageBodyStyle.Wrapped)]
        public string CreateRelationship(string repositoryId, CmisProperties properties, string[] policies, AccessControlList addACEs, AccessControlList removeACEs)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Entry;

            // Repository ID and properties must be specified
            if (String.IsNullOrEmpty(repositoryId) || properties == null)
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and properties must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Create the relationship object
            return ObjectProviderManager.Provider.CreateRelationship(repositoryId, properties, policies, addACEs, removeACEs);
        }

        /// <summary>
        /// Creates a policy object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="properties">The property values that must be applied to the newly-created relationship object.</param>
        /// <param name="folderId">The id of the folder that must be parent folder for the newly-created policy object.</param>
        /// <param name="policies">A list of policy ids that must be applied to the newly-created relationship object. (Optional)</param>
        /// <param name="addACEs">A list of ACEs that must be applied to the newly-created policy object, either using ACL from folderId if specified, or being applied if no folder is specified. (Optional)</param>
        /// <param name="removeACEs">A list of ACEs that must be removed from the newly-created policy object, either using ACL from folderId if specified, or being applied if no folder is specified. (Optional)</param>
        /// <returns>A <see cref="System.String"/> holding the id of the newly created policy object.</returns>
        //[WebInvoke(Method = "POST", UriTemplate = "/{repositoryId}/policy")]
        public Atom10ItemFormatter CreatePolicy(string repositoryId, SyndicationItem inputEntry)
        {
            string folderId = RequestParameter.FolderId;

            IncomingWebRequestContext ictx = WebOperationContext.Current.IncomingRequest;
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;

            string contentType = ictx.ContentType;

            CmisObject inputObject = CmisSyndicationItem.GetObject(inputEntry);

            // Repository ID must be specified. Policy properties must not be null
            if (String.IsNullOrEmpty(repositoryId) || inputObject.Properties == null)
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and properties must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Store the created policy as a CMIS object
            // TODO: fetch and add policies, addACEs, removeACEs
            CmisObject outputObject = ObjectProviderManager.Provider.CreatePolicy(repositoryId, inputObject.Properties, folderId, null, null, null);

            // Convert the object to a syndication item
            CmisSyndicationItem outputEntry = CmisSyndicationItem.CreateEntry(repositoryId, outputObject);

            // Response properties
            ctx.ContentType = CmisContentType.Entry;
            ctx.StatusCode = HttpStatusCode.Created;
            ctx.Headers.Add("Location", string.Format("{0}{1}/object/{2}", _baseUri.AbsoluteUri, repositoryId, outputObject.Id));

            // Format the entry as an Atom entry
            return new Atom10ItemFormatter(outputEntry);
        }

        /// <summary>
        /// Gets allowable actions for an object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object to get allowable actions for.</param>
        /// <returns>An <see cref="NCMIS.ObjectModel.MetaData.AllowableActions"/> object.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/object/{objectId}/allowableactions")]
        public AllowableActions GetAllowableActions(string repositoryId, string objectId)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.AllowableActions;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and objectId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Return the allowable actions for the object
            return ObjectProviderManager.Provider.GetAllowableActions(repositoryId, objectId);
        }

        /// <summary>
        /// Gets the specified object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="filter">Value indicating which properties for Objects that must be returned. (Optional, default: Not set)</param>
        /// <param name="includeAllowableActions">If true, then the the available actions for each object in the result set must be returned. (Optional, default: false)</param>
        /// <param name="includeRelationships">Value indicating what relationships in which the objects returned participate that must be returned, if any. (Optional, default: none)</param>
        /// <param name="renditionFilter">Renditions whose kind matches this filter must be returned. (Optional, default: cmis:none)</param>
        /// <param name="includePolicyIds">If true, then the Repository must include the IDs of Policies applied to the object referenced in each change event, if the change
        /// event modified the set of policies applied to the object. (Optional, default: false)</param>
        /// <param name="includeACL">If true, then the repository must return the ACLs for each object in the result set. (Optional, default: false)</param>
        /// <returns>An instance of <see cref="NCMIS.ObjectModel.CmisObject"/>, formatted as Atom 1.0 Entry.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/object/{objectId}")]
        public Atom10ItemFormatter GetObject(string repositoryId, string objectId)
        {
            string filter = RequestParameter.Filter;
            bool includeAllowableActions = RequestParameter.IncludeAllowableActions;
            IncludeRelationships includeRelationships = RequestParameter.IncludeRelationships;
            string renditionFilter = RequestParameter.RenditionFilter;
            bool includePolicyIds = RequestParameter.IncludePolicyIds;
            bool includeAcl = RequestParameter.IncludeAcl;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Entry;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and objectId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Retrieve the specified object
            CmisObject cmisObject = ObjectProviderManager.Provider.GetObject(repositoryId, objectId, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl);

            // Convert the object to a syndication item
            CmisSyndicationItem entry = CmisSyndicationItem.CreateEntry(repositoryId, cmisObject);

            // Format the entry as an Atom entry
            return new Atom10ItemFormatter(entry);
        }

        /// <summary>
        /// Gets specified object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="path">The path to the object.</param>
        /// <param name="filter">Value indicating which properties for Objects that must be returned. (Optional, default: Not set)</param>
        /// <param name="includeAllowableActions">>If true, then the the available actions for each object in the result set must be returned. (Optional, default: false)</param>
        /// <param name="includeRelationships">Value indicating what relationships in which the objects returned participate that must be returned, if any. (Optional, default: none)</param>
        /// <param name="renditionFilter">Renditions whose kind matches this filter must be returned. (Optional, default: cmis:none)</param>
        /// <param name="includePolicyIds">If true, then the Repository must include the IDs of Policies applied to the object referenced in each change event, if the change
        /// event modified the set of policies applied to the object. (Optional, default: false)</param>
        /// <param name="includeACL">If true, then the repository must return the ACLs for each object in the result set. (Optional, default: false)</param>
        /// <returns>An instance of <see cref="NCMIS.ObjectModel.CmisObject"/>, formatted as Atom 1.0 Entry.</returns>
        [WebGet(UriTemplate = CmisUriTemplate.ObjectByPath)]
        public Atom10ItemFormatter GetObjectByPath(string repositoryId, string path, string filter, bool includeAllowableActions, string includeRelationships, string renditionFilter, bool includePolicyIds, bool includeACL)
        {
            IncludeRelationships _includeRelationships = IncludeRelationships.None;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Entry;

            // Repository ID and path must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(path))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and path must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Retrieve the specified object
            CmisObject cmisObject = ObjectProviderManager.Provider.GetObjectByPath(repositoryId, path, filter, includeAllowableActions, _includeRelationships, renditionFilter, includePolicyIds, includeACL);

            // Convert the object to a syndication item
            CmisSyndicationItem entry = CmisSyndicationItem.CreateEntry(repositoryId, cmisObject);
            entry.AddObject(cmisObject);

            // Format the entry as an Atom entry
            return new Atom10ItemFormatter(entry);
        }

        /// <summary>
        ///  Gets the content stream for the specified Document object, or gets a rendition stream for a specified rendition of
        ///  a document or folder object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="streamId">The identifier for the rendition stream, when used to get a rendition stream. For Documents,
        /// if not provided then this method returns the content stream. For Folders, it must be provided.</param>
        /// <returns></returns>
        [WebGet(UriTemplate = "/{repositoryId}/object/{objectId}/content")]
        public Stream GetContentStream(string repositoryId, string objectId)
        {
            // TODO: For Documents, if not provided then this method returns the content stream. For Folders, it MUST be provided.
            string streamId = RequestParameter.StreamId;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and objectId must not be null or empty.", ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Get the content stream and return it as a memory stream
            ContentStream contentStream = ObjectProviderManager.Provider.GetContentStream(repositoryId, objectId, streamId);
            if (contentStream != null)
            {
                ctx.ContentType = contentStream.MimeType;
                if (contentStream.Length != null)
                {
                    ctx.ContentLength = (long)contentStream.Length;
                }
                return new MemoryStream(contentStream.Stream) as Stream;
            }
            return null;
        }

        // NOTE: Not exposed in the REST/Atom binding. See 3.1.9 Services not Exposed.
        [WebGet(UriTemplate = "/{repositoryId}/renditions/{objectId}/?renditionFilter={renditionFilter}&maxItems={maxItems}&skipCount={skipCount}")]
        public Renditions GetRenditions(string repositoryId, string objectId, string renditionFilter, string maxItems, string skipCount)
        {
            return null;
        }

        /// <summary>
        /// Updates the properties of specified object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="properties">The updated property values that must be applied to the Object.</param>
        /// <param name="changeToken">Speicifes change token used for optimistic locking and/or concurrency checking to ensure that
        /// user updates do not conflict. (Optional)</param>
        /// <returns>A <see cref="NCMIS.ObjectModel.TokenedCmisObjectId"/> containing the id of the updated object.</returns>
        [WebInvoke(Method = "PUT", UriTemplate = "/{repositoryId}/object/{objectId}")]
        public TokenedCmisObjectId UpdateProperties(string repositoryId, string objectId, Stream stream)
        {
            // , BodyStyle = WebMessageBodyStyle.Wrapped

            string changeToken = null;

            IncomingWebRequestContext ictx = WebOperationContext.Current.IncomingRequest;
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;

            SyndicationItem syndicationItem;
            string contentType = ictx.ContentType;

            // Extract the object with the new properties
            using (XmlReader reader = XmlReader.Create(stream))
            {
                try
                {
                    syndicationItem = SyndicationItem.Load(reader);
                }
                catch (XmlException)
                {
                    ctx.StatusCode = ServiceException.InvalidArgument;
                    return null;
                }
            }
            CmisObject cmisObject = CmisSyndicationItem.GetObject(syndicationItem);

            // Repository ID and object ID must be specified. Properties must not be null
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId) || cmisObject.Properties == null)
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId, objectId and properties must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Update the specified object's properties
            // TODO: Fetch changeToken
            return ObjectProviderManager.Provider.UpdateProperties(repositoryId, objectId, cmisObject.Properties, changeToken);
        }

        /// <summary>
        /// Moves the specified file-able object from one folder to another.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="targetFolderId">The folder into which the object is to be moved.</param>
        /// <param name="sourceFolderId">The folder from which the object is to be moved.</param>
        [WebInvoke(Method = "POST", UriTemplate = "/{repositoryId}/object/{objectId}/{targetFolderId}/{sourceFolderId}/", BodyStyle = WebMessageBodyStyle.Wrapped)]
        public void MoveObject(string repositoryId, string objectId, string targetFolderId, string sourceFolderId)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            //ctx.ContentType = CmisContentType.Entry;

            // Repository ID, object ID, target folder ID, source folder ID must all be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId) || String.IsNullOrEmpty(targetFolderId) || String.IsNullOrEmpty(sourceFolderId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId, objectId, targetFolderId and sourceFolderId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return;
            }

            // Move the folder to target folder
            ObjectProviderManager.Provider.MoveObject(repositoryId, objectId, targetFolderId, sourceFolderId);
        }

        /// <summary>
        /// Deletes specified object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object to delete.</param>
        /// <param name="allVersions">If true, all versions of the document object will be deleted. If false, only the document object specified will be deleted.
        /// The value of this parameter is ignored when invoked on a non-document object or a non-versionable document object. (Optional, default: true)</param>
        [WebInvoke(Method = "DELETE", UriTemplate = "/{repositoryId}/object/{objectId}")]
        public void DeleteObject(string repositoryId, string objectId)
        {
            bool allVersions = RequestParameter.AllVersions;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and objectId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return;
            }

            // Delete the specified object
            ObjectProviderManager.Provider.DeleteObject(repositoryId, objectId, allVersions);
        }

        /// <summary>
        /// Deletes the specified folder object and all of its child- and descendant-objects.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="folderId">The id of the folder to delete.</param>
        /// <param name="allVersions">If true, all versions of the document object will be deleted. If false, only the document object specified will be deleted.
        /// The value of this parameter is ignored when invoked on a non-document object or a non-versionable document object. (Optional, default: true)</param>
        /// <param name="unfileObject">Specifies how the repository must process file-able child- or descendant-objects. (Optional, default: UnfileObject.Delete)</param>
        /// <param name="continueOnFailure">If true then the repository should continute attempting to perform this operation even if deletion of a child- or
        /// descendant-object in the specified folder cannot be deleted. If false, the the repository should abort this method when it fails to delete a single child-
        /// or descendant-object. (Optional, default: false)</param>
        /// <returns>An array of <see cref="System.String"/> with ids of objects in the folder tree that was not deleted.</returns>
        [WebInvoke(Method = "DELETE", UriTemplate = "/{repositoryId}/descendants/{folderId}")]
        public string[] DeleteTree(string repositoryId, string folderId)
        {
            bool allVersions = RequestParameter.AllVersions;
            UnfileObjects unfileObjects = RequestParameter.UnfileObjects;
            bool continueOnFailure = RequestParameter.ContinueOnFailure;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            //ctx.ContentType = CmisContentType.Entry;

            // Repository ID and folder ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(folderId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and folderId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Delete the folder tree
            return ObjectProviderManager.Provider.DeleteTree(repositoryId, folderId, allVersions, unfileObjects, continueOnFailure);
        }

        /// <summary>
        /// Sets the content stream for specified document object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">the id fo the document object.</param>
        /// <param name="contentStream">The content stream.</param>
        /// <param name="overwriteFlag"> If true, then the repository must replace the existing content stream for the object (if any) with the input contentStream.
        /// If false, then the repository must only set the input contentStream for the object if the object currently does not have a content-stream. (Optional, default: true)</param>
        /// <param name="changeToken">Specifies change token used for optimistic locking and/or concurrency checking to ensure that
        /// user updates do not conflict. (Optional)</param>
        /// <returns>A <see cref="NCMIS.ObjectModel.TokenedDocumentId"/> containing the id of the document.</returns>
        [WebInvoke(Method = "PUT", UriTemplate = "/{repositoryId}/object/{objectId}/content")]
        public TokenedDocumentId SetContentStream(string repositoryId, string objectId, Stream stream)
        {
            bool? overwriteFlag = RequestParameter.OverwriteFlag;

            // TODO: Get stream: ContentStream contentStream, string changeToken --> stream (part of cmisobject)

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            //ctx.ContentType = CmisContentType.Entry;

            // Repository ID and object ID must be specified. New stream must not be null
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId) || stream == null)
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId, objectId and contentStream must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Set the new content stream
            // TODO: get contentstream, changetoken
            return ObjectProviderManager.Provider.SetContentStream(repositoryId, objectId, null, overwriteFlag, null);
        }

        /// <summary>
        /// Deletes the content stream for the specified Document object.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the document object.</param>
        /// <param name="changeToken">Specifies change token used for optimistic locking and/or concurrency checking to ensure that
        /// user updates do not conflict. (Optional)</param>
        [WebInvoke(Method = "DELETE", UriTemplate = "/{repositoryId}/object/{objectId}/content")]
        public TokenedDocumentId DeleteContentStream(string repositoryId, string objectId)
        {
            string changeToken = RequestParameter.ChangeToken;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            //ctx.ContentType = CmisContentType.Entry;

            // Repository ID and object ID must be specified.
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId, objectId and contentStream must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Delete the content stream
            return ObjectProviderManager.Provider.DeleteContentStream(repositoryId, objectId, changeToken);
        }
    }
}
